#include "preHeader.h"
#include "Scene.h"

struct NavMeshSetHeader
{
	int magic;
	int version;
	int numTiles;
	dtNavMeshParams params;
};

struct NavMeshTileHeader
{
	dtTileRef tileRef;
	int dataSize;
};

const float Scene::m_polyPickExtents[2][3] = {
	{0.f, 3.f, 0.f,},
	{0.f, 3.f, 0.f,},
};

Scene::Scene()
: m_navMesh(NULL)
{
}

Scene::~Scene()
{
	dtFreeNavMesh(m_navMesh);
	dtNavMeshQuery* pNavMeshQuery = NULL;
	while ((pNavMeshQuery = m_navMeshQueryPool.Get()) != NULL) {
		dtFreeNavMeshQuery(pNavMeshQuery);
	}
}

bool Scene::Init(const std::string& sceneDir)
{
	return LoadSceneBound(sceneDir) &&
		LoadNavMesh(sceneDir);
}

bool Scene::LoadSceneBound(const std::string& sceneDir)
{
	std::string sceneFile = GetSceneBoundFile(sceneDir);
	std::ifstream stream(sceneFile);
	if (!stream.is_open()) {
		ELOG("Open scene bound file `%s` failed.", sceneFile.c_str());
		return false;
	}

	stream >> m_sceneBound.min.x >> m_sceneBound.min.y
		>> m_sceneBound.max.x >> m_sceneBound.max.y;

	return true;
}

bool Scene::LoadNavMesh(const std::string& sceneDir)
{
	m_navMesh = dtAllocNavMesh();

	std::string sceneFile = GetNavMeshFile(sceneDir);
	std::ifstream stream(sceneFile, std::ios::binary | std::ios::ate);
	if (!stream.is_open()) {
		ELOG("Open navmesh file `%s` failed.", sceneFile.c_str());
		return false;
	}

	size_t fileSize = stream.tellg();
	if (fileSize < sizeof(NavMeshSetHeader)) {
		ELOG("navmesh file `%s` data invalid.", sceneFile.c_str());
		return false;
	}

	std::string fileData(fileSize, '\0');
	stream.seekg(0, std::ios::beg);
	stream.read(&fileData[0], fileSize);
	if (stream.gcount() != fileSize) {
		ELOG("Read navmesh file `%s` failed.", sceneFile.c_str());
		return false;
	}

	NavMeshSetHeader header;
	memcpy(&header, &fileData[0], sizeof(header));
	if (header.version != 1) {
		ELOG("navmesh file `%s` version invalid.", sceneFile.c_str());
		return false;
	}

	dtStatus status = m_navMesh->init(&header.params);
	if (dtStatusFailed(status)) {
		ELOG("navmesh file `%s` data invalid.", sceneFile.c_str());
		return false;
	}

	size_t offset = sizeof(header);
	for (int i = 0; i < header.numTiles; ++i) {
		NavMeshTileHeader tileHeader;
		memcpy(&tileHeader, &fileData[offset], sizeof(tileHeader));
		offset += sizeof(tileHeader);
		auto tileData = (u8*)dtAlloc(tileHeader.dataSize, DT_ALLOC_PERM);
		memcpy(tileData, &fileData[offset], tileHeader.dataSize);
		offset += tileHeader.dataSize;
		auto status = m_navMesh->addTile(tileData,
			tileHeader.dataSize, DT_TILE_FREE_DATA, tileHeader.tileRef, NULL);
		if (dtStatusFailed(status)) {
			ELOG("navmesh file `%s` data invalid.", sceneFile.c_str());
			return false;
		}
	}

	return true;
}

std::string Scene::GetSceneBoundFile(const std::string& sceneDir)
{
	return sceneDir + "/scene.bound";
}

std::string Scene::GetNavMeshFile(const std::string& sceneDir)
{
	return sceneDir + "/scene.navmesh";
}

int Scene::FindStraightPath(const vector3f& startPos, const vector3f& endPos,
	vector3f straightPath[], int maxStraightPathSize, int excludeFlags)
{
#if 0
	dtNavMeshQuery* pNavMeshQuery = GetNavMeshQuery();
	_defer(PutNavMeshQuery(pNavMeshQuery));

	dtQueryFilter filter;
	filter.setExcludeFlags(excludeFlags);

	dtPolyRef startRef;
	dtStatus status = pNavMeshQuery->findNearestPoly(
		startPos.Data(), m_polyPickExtents[0], &filter, &startRef, NULL);
	if (dtStatusFailed(status)) {
		return -1;
	}

	dtPolyRef endRef;
	status = pNavMeshQuery->findNearestPoly(
		endPos.Data(), m_polyPickExtents[1], &filter, &endRef, NULL);
	if (dtStatusFailed(status)) {
		return -1;
	}

	int npolys = 0;
	dtPolyRef polys[NAV_PATH_MAX_POLYS];
	status = pNavMeshQuery->findPath(
		startRef, endRef, startPos.Data(), endPos.Data(),
		&filter, polys, &npolys, ARRAY_SIZE(polys));
	if (dtStatusFailed(status)) {
		return -1;
	}

	int nstraightpath = 0;
	status = pNavMeshQuery->findStraightPath(
		startPos.Data(), endPos.Data(), polys, npolys,
		straightPath[0].Data(), NULL, NULL, &nstraightpath,
		maxStraightPathSize);
	if (dtStatusFailed(status)) {
		return -1;
	}

	return nstraightpath;
#else
	straightPath[0] = startPos;
	straightPath[1] = endPos;
	return 2;
#endif
}

int Scene::Raycast(const vector3f& startPos, const vector3f& endPos,
	vector3f& hitPos, int excludeFlags)
{
#if 0
	dtNavMeshQuery* pNavMeshQuery = GetNavMeshQuery();
	_defer(PutNavMeshQuery(pNavMeshQuery));

	dtQueryFilter filter;
	filter.setExcludeFlags(excludeFlags);

	dtPolyRef startRef;
	dtStatus status = pNavMeshQuery->findNearestPoly(
		startPos.Data(), m_polyPickExtents[0], &filter, &startRef, NULL);
	if (dtStatusFailed(status)) {
		return -1;
	}

	dtRaycastHit hit;
	status = pNavMeshQuery->raycast(
		startRef, startPos.Data(), endPos.Data(), &filter, 0, &hit);
	if (dtStatusFailed(status)) {
		return -1;
	}

	if (hit.t >= 1.f) {
		hitPos = endPos;
	} else {
		hitPos = startPos + (endPos - startPos) * hit.t;
	}

	return 0;
#else
	hitPos = endPos;
	return 0;
#endif
}

dtNavMeshQuery* Scene::GetNavMeshQuery()
{
	auto pNavMeshQuery = m_navMeshQueryPool.Get();
	if (pNavMeshQuery == NULL) {
		pNavMeshQuery = dtAllocNavMeshQuery();
		pNavMeshQuery->init(m_navMesh, 65535);
	}
	return pNavMeshQuery;
}

void Scene::PutNavMeshQuery(dtNavMeshQuery* pNavMeshQuery)
{
	if (!m_navMeshQueryPool.Put(pNavMeshQuery)) {
		dtFreeNavMeshQuery(pNavMeshQuery);
	}
}
